package de.uni_saarland.coli.doclists.index.filesystem;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * 
 * @author stefan
 * 
 *         Caches objects.
 * 
 * @param <T>
 *            Type of cached objects (needs to implement IToBytes)
 */
public class ObjectCache<T> {
	/**
	 * maps ids to objects index in cache
	 */
	private Map<Integer, Integer> objectMap;

	/**
	 * the objects cached + infos
	 */
	private List<ObjectCacheDataItem<T>> objectRank;

	int size;

	/**
	 * Constructor.
	 * 
	 * @param size
	 *            size if the cache
	 */
	public ObjectCache(int size) {
		this.objectMap = new HashMap<Integer, Integer>(size);
		this.objectRank = new ArrayList<ObjectCacheDataItem<T>>();
		this.size = size;
	}

	/**
	 * Increases the priority of an object
	 * 
	 * @param i
	 *            the index of the object in the list
	 * @return the object
	 */
	private T increasePriority(int i) {
		ObjectCacheDataItem<T> item = this.objectRank.get(i);
		ObjectCacheDataItem<T> prevItem = this.objectRank.get(i - 1);

		item.increaseCount();

		if (item.getFrequency() >= prevItem.getFrequency()) {
			// System.out.println("swap " + i + " and " + (i - 1));
			this.objectMap.put(item.getID(), i - 1);
			this.objectMap.put(prevItem.getID(), i);

			this.objectRank.remove(i - 1);

			this.objectRank.add(i, prevItem);
		}
		return item.getItem();
	}

	/**
	 * Returns an object. Causes disc io if the object is not in the cache
	 * 
	 * @param id
	 *            The id of the object
	 * @return the object. Null if no object with the id exists
	 */
	public T get(int id) {
		if (this.objectMap.containsKey(id)) {
			int pos = this.objectMap.get(id);
//			System.out.println("get " + id + " at " + pos + " # " + this.size);
			if (pos != 0) {
				// System.out.println("inc");
				T o = this.increasePriority(pos);
				// System.out.println(this.toString());
				return o;

			} else {
				ObjectCacheDataItem<T> item = this.objectRank.get(pos);
				//item.increaseCount();
				return item.getItem();
			}
		}
		return null;
	}

	/**
	 * Adds an object to the cache
	 * 
	 * @param id
	 *            the objects id
	 * @param object
	 *            the objects
	 */
	public void add(int id, T object) {
		// System.out.println("add " + this.objectRank.size() + " < " +
		// this.size);

		if (this.objectMap.containsKey(id))
			return;

		if (this.objectRank.size() == this.size) {
//			System.out.println("remove last");
			ObjectCacheDataItem<T> item = this.objectRank.get(this.size - 1);

			this.objectMap.remove(item.getID());
			this.objectRank.remove(this.size - 1);
		}

		int pos = this.objectRank.size();

		this.objectMap.put(id, pos);
		this.objectRank.add(new ObjectCacheDataItem<T>(id, object));

		// System.out.println("Add " + id + " to cache");
		// System.out.println(this.toString());

	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		for (Entry<Integer, Integer> entry : this.objectMap.entrySet()) {
			sb.append(entry.getKey());
			sb.append(" at ");
			sb.append(entry.getValue());
			sb.append(" is ");
			sb.append(this.objectRank.get(entry.getValue()).toString());
			sb.append("\n");
		}

		return sb.toString();
	}
}
